package jEDF.JEDF;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.event.*;

import jEDF.FFT.*;
import jEDF.Hypnogram.*;
import jEDF.Montage.*;
import jEDF.PreciseScoring.*;

/**
 *
 * <p>Titre : jEDF</p>
 * <p>Description : Java European Data Format Viewer and Analyser</p>
 * <p>Author : Nizar Kerkeni</p>
 * <p>Copyright : Copyright (c) 2003-2006</p>
 * <p>Version 2.0</p>
 */

public class Polysomnograph extends JPanel {
    private Montage montage = new Montage();

    private final Color yAxisColorSecond = Color.LIGHT_GRAY;
    private final Color timeCaptionColor = Color.RED;
    private final Color selectionColor = Color.RED;

    private float dashLength = 5f;

    //private int nbSecondsToDisplay = 0;
    private int totalDuration = 0;

    private PreciseScoring preciseScoring = null;
    private JEDFMainWindow mainWindow = null;
    private JEDFSignalCaption signalCaption = null;
    //private Polysomnograph self = this;

    public class EpochDurationComboBox extends JComboBox {
        private int[] durations = {0, 1, 2, 3, 4, 5, 10, 20, 30, 60, 120, 180, 240};

        public EpochDurationComboBox() {
            addItem("1cm");
            for (int i = 1; i < durations.length; i++) {
                addItem(getTimeString(durations[i]));
            }
        }

        public int getDuration() {
            if (getSelectedIndex() == 0) {
                return get1CentimeterDuration();
            }

            return durations[getSelectedIndex()];
        }

        public void setDuration(int duration) {
            setSelectedItem(getTimeString(duration));
        }

        private String getTimeString(int time) {
            if (time >= 60) {
                time /= 60;
                return time + "m";
            }
            return time + "s";
        }
    };

    private boolean isInterpolated = false;

    private JSlider navigationSlider = new JSlider();
    private EpochDurationComboBox epochDurationComboBox = new
            EpochDurationComboBox();

    private FFTAnalysisDialog fftAnalysisDialog = new FFTAnalysisDialog(this);

    /**
     * @param hypnogram the corresponding hypnogram
     */
    public Polysomnograph(JEDFMainWindow pMainWindow, JEDFHypnogram hypnogram) {
        //this.hypnogram=hypnogram;
        this.mainWindow = pMainWindow;
        hypnogram.setPolysomnograph(this);

        setBackground(Color.white);

        navigationSlider.setSnapToTicks(true);
        navigationSlider.setPaintTicks(true);

        navigationSlider.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent arg0) {
                mainWindow.displayedRecordModified();
                DefaultKeyboardFocusManager.getCurrentKeyboardFocusManager().
                        clearGlobalFocusOwner();
                //preciseScoring.setFocus(false);
            }
        });

        navigationSlider.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent arg0) {
                super.mouseClicked(arg0);
                DefaultKeyboardFocusManager.getCurrentKeyboardFocusManager().
                        clearGlobalFocusOwner();
            }
        });

        addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent arg0) {
                super.mousePressed(arg0);
                onMousePressed(arg0);
            }

            public void mouseReleased(MouseEvent arg0) {
                super.mouseReleased(arg0);
                onMouseReleased(arg0);
            }
        });

        addMouseMotionListener(new MouseMotionAdapter() {

            public void mouseDragged(MouseEvent arg0) {
                onMouseDragged(arg0);
            }
        });

        epochDurationComboBox.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent arg0) {
                int duration = epochDurationComboBox.getDuration();

                setEpochDuration(duration);
                navigationSlider.setMajorTickSpacing(duration);
                navigationSlider.setSnapToTicks(true);

                DefaultKeyboardFocusManager.getCurrentKeyboardFocusManager().
                        clearGlobalFocusOwner();
                mainWindow.displayedRecordModified();
                //preciseScoring.setFocus(false);
            }
        });

        epochDurationComboBox.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent arg0) {
                super.mouseClicked(arg0);
                DefaultKeyboardFocusManager.getCurrentKeyboardFocusManager().
                        clearGlobalFocusOwner();
            }
        });

        fftAnalysisDialog.addComponentListener(new ComponentAdapter() {
            /* (non-Javadoc)
             * @see java.awt.event.ComponentAdapter#componentHidden(java.awt.event.ComponentEvent)
             */
            public void componentHidden(ComponentEvent arg0) {
                super.componentHidden(arg0);
                repaint();
            }
        });
    }

    /**
     * load an edf file, displaying all of its signals
     * @param edfFile the EDF file to load
     */
    public void load(jEDF.EDF.EDFFile edfFile) {
        totalDuration = edfFile.getNbRecords() * edfFile.getRecordDuration();

        montage.loadEDFFile(this, edfFile);

        setEpochDuration(edfFile.getRecordDuration());

        navigationSlider.setMinimum(0);
        navigationSlider.setValue(0);

        navigationSlider.setMaximum(totalDuration - 1);

    }

    private void drawYAxis(Graphics2D g) {
        int nbSecondsToDisplay = epochDurationComboBox.getDuration();

        if (montage.getNbSignals() > 0) {
            Color currentColor = g.getColor();

            g.setColor(yAxisColorSecond);

            double width = getWidth();

            double intervalBetweenYAxis = width / (double) nbSecondsToDisplay;

            if (intervalBetweenYAxis < g.getFontMetrics().stringWidth("88")) {
                //return;
                intervalBetweenYAxis *= 30.0;
            }

            Stroke tempStroke = g.getStroke();

            float[] dash = {dashLength, dashLength};
            g.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_SQUARE,
                                        BasicStroke.JOIN_BEVEL, 1f, dash, 0f));

            for (double x = 0; x < getWidth(); x += intervalBetweenYAxis) {
                g.drawLine((int) Math.round(x), 0, (int) Math.round(x),
                           getHeight());
            }

            g.setStroke(tempStroke);
            g.setColor(currentColor);
        }
    }

    private void drawTimeCaption(Graphics2D g) {
        if (montage.getNbSignals() == 0) {
            return;
        }

        int nbSecondsToDisplay = epochDurationComboBox.getDuration();

        Color currentColor = g.getColor();

        g.setColor(timeCaptionColor);

        int strLength = 0;

        float intervalBetweenYAxis = (float) getWidth() /
                                     (float) nbSecondsToDisplay;
        int coef = 1;
        if (intervalBetweenYAxis < g.getFontMetrics().stringWidth("88")) {
            //return;
            intervalBetweenYAxis *= 30;
            coef = 30;
        }

        for (int i = 1; i <= nbSecondsToDisplay; i++) {
            strLength = g.getFontMetrics().stringWidth("" + i);
            String str = "";
            if ((i * coef) >= 60) {
                str = (i * coef) / 60 + "m " + (i * coef) % 60 + "s";
            }
            else {
                str = (i * coef) + "";
            }

            g.drawString(str,
                         (int) ((i * intervalBetweenYAxis) -
                                ((intervalBetweenYAxis + strLength) / 2)),
                         getHeight() - 1);
        }

        g.setColor(currentColor);
    }


    /* (non-Javadoc)
     * @see java.awt.Container#paintComponents(java.awt.Graphics)
     */
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2D = (Graphics2D) g;

        if (!isInterpolated) {
            /** D�sactivation de l'anti-aliasing */
            g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                 RenderingHints.VALUE_ANTIALIAS_OFF);
            g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                                 RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
            /** Demande de rendu rapide */
            g2D.setRenderingHint(RenderingHints.KEY_RENDERING,
                                 RenderingHints.VALUE_RENDER_SPEED);
            g2D.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
                                 RenderingHints.VALUE_COLOR_RENDER_SPEED);
            g2D.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
                                 RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
            g2D.setRenderingHint(RenderingHints.KEY_DITHERING,
                                 RenderingHints.VALUE_DITHER_DISABLE);
        }
        else {
            /** Activation de l'anti-aliasing */
            g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                 RenderingHints.VALUE_ANTIALIAS_ON);
            g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                                 RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            /** Demande de rendu de qualit� */
            g2D.setRenderingHint(RenderingHints.KEY_RENDERING,
                                 RenderingHints.VALUE_RENDER_QUALITY);
            g2D.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
                                 RenderingHints.VALUE_COLOR_RENDER_QUALITY);
            g2D.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
                                 RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            g2D.setRenderingHint(RenderingHints.KEY_DITHERING,
                                 RenderingHints.VALUE_DITHER_ENABLE);
        }

        if (montage == null) {
            return;
        }

        drawYAxis(g2D);

        JEDFGraph[] tabGraph = montage.getAllGraph();

        float heightOfAGraph = getGraphHeight();

        for (int i = 0; i < tabGraph.length; i++) {
            tabGraph[i].draw(g2D, i * heightOfAGraph);
        }

        drawSelection(g2D);

        drawTimeCaption(g2D);
    }

    /**
     * @param nbSeconds set the numbers of seconds to be displayed, i.e. the
     * current epoch duration
     */
    public void setEpochDuration(int nbSeconds) {
        epochDurationComboBox.setDuration(nbSeconds);

        mainWindow.displayedRecordModified();
    }

    /**
     * clear the polysomnograph, removing all the signals
     */
    public void clear() {
        montage.clear();
        navigationSlider.setValue(0);
        epochDurationComboBox.setDuration(0);
    }

    /**
     * set the first second to display
     * @param second (0..n-1)
     */
    public void setStartSecond(int second) {
        navigationSlider.setValue(second < 0 ? 0 : second);

        int startSecond = navigationSlider.getValue();
        int nbSecondsToDisplay = epochDurationComboBox.getDuration();

        startSecond /= nbSecondsToDisplay;
        startSecond *= nbSecondsToDisplay;

        if (startSecond >= totalDuration) {
            startSecond = (totalDuration - 1) / nbSecondsToDisplay;
            startSecond *= nbSecondsToDisplay;
            navigationSlider.setValue(startSecond);
        }
    }

    /**
     * go to the next record, i.e. the next epoch
     */
    public void incrementStartRecord() {
        if (!isItTheEnd()) {
            setStartSecond(navigationSlider.getValue() +
                           epochDurationComboBox.getDuration());
        }
        //setStartSecond(navigationSlider.getValue()+nbSecondsToDisplay);
    }

    /**
     * go to the previous record, i.e. the previous epoch
     */
    public void decrementStartRecord() {
        if (!isItTheBeginning()) {
            setStartSecond(navigationSlider.getValue() -
                           epochDurationComboBox.getDuration());
        }
        //setStartSecond(navigationSlider.getValue()-nbSecondsToDisplay);
    }

    /**
     * display the beginning of the EDF file
     */
    public void firstStartSecond() {
        setStartSecond(0);
    }

    /**
     * display the end of the EDF file
     */
    public void lastStartSecond() {
        setStartSecond(Integer.MAX_VALUE);
    }

    /**
     * @return the current start second
     */
    public int getStartSecond() {
        return navigationSlider.getValue();
    }

    /**
     * @return an array containing all the currently displayed graphs
     */
    public JEDFGraph[] getAllGraphs() {
        return montage.getAllGraph();
    }

    /**
     * Load the specified montage or the default montage if parameter is null.
     * @param montage the montage to load
     */
    public void loadMontage(Montage montage) {
        if (montage == null) {
            this.montage = Montage.getDefaultMontage();
            montage = this.montage;
        }
        else {
            this.montage = montage;
        }

        if (signalCaption != null) {
            signalCaption.repaint();
        }
        mainWindow.displayedRecordModified();
    }

    /**
     * @return Returns the number of seconds to display.
     */
    public int getEpochDuration() {
        return epochDurationComboBox.getDuration(); //nbSecondsToDisplay;
    }

    /**
     * @return the height of a graph
     */
    public float getGraphHeight() {
        float currentHeight = (float) SwingUtilities.calculateInnerArea(this,
                new Rectangle()).getHeight();
        currentHeight--;
        return currentHeight / (float) montage.getNbSignals();
    }

    /**
     * increase the height of all displayed graphs
     */
    public void zoomIn() {
        for (int i = 0; i < montage.getNbSignals(); i++) {
            montage.getGraph(i).increaseVerticalSize();
        }
    }

    /**
     * decrease the height of all displayed graphs
     */
    public void zoomOut() {
        for (int i = 0; i < montage.getNbSignals(); i++) {
            montage.getGraph(i).decreaseVerticalSize();
        }
    }

    /**
     * reset the height of all displayed graphs to their original size
     */
    public void zoomReset() {
        for (int i = 0; i < montage.getNbSignals(); i++) {
            montage.getGraph(i).resetVerticalSize();
        }
    }

    /**
     * @return true if the end of the data has been reached
     */
    public boolean isItTheEnd() {
        return navigationSlider.getValue() >=
                (totalDuration - epochDurationComboBox.getDuration()); //nbSecondsToDisplay);
    }

    /**
     * @return true if we are at the beginning of the data
     */
    public boolean isItTheBeginning() {
        return navigationSlider.getValue() == 0;
    }

    /**
     * @param preciseScoring The preciseScoring to set.
     */
    public void setPreciseScoring(PreciseScoring preciseScoring) {
        this.preciseScoring = preciseScoring;
    }

    /**
     * @return Returns the preciseScoring.
     */
    public PreciseScoring getPreciseScoring() {
        return preciseScoring;
    }

    private int get1CentimeterDuration() {
        double width = SwingUtilities.calculateInnerArea(this, new Rectangle()).
                       getWidth();
        width /= (Toolkit.getDefaultToolkit().getScreenResolution() / 2.54);
        return (int) Math.round(width);
    }

    /**
     * @return Returns the navigationSlider.
     */
    public JSlider getNavigationSlider() {
        return navigationSlider;
    }

    /**
     * @param signalCaption The signalCaption to set.
     */
    public void setSignalCaption(JEDFSignalCaption signalCaption) {
        this.signalCaption = signalCaption;
    }

    /**
     * @return Returns the epochDurationComboBox.
     */
    public EpochDurationComboBox getEpochDurationComboBox() {
        return epochDurationComboBox;
    }

    public void setAllGraphsInverted(boolean areInverted) {
        for (int i = 0; i < montage.getNbSignals(); i++) {
            montage.getGraph(i).setInverted(areInverted);
        }
    }

    /**
     * @return Returns the isInterpolated.
     */
    public boolean isInterpolated() {
        return isInterpolated;
    }

    /**
     * @param isInterpolated The isInterpolated to set.
     */
    public void setInterpolated(boolean isInterpolated) {
        this.isInterpolated = isInterpolated;
        repaint();
    }


    private JEDFGraph chosenGraph = null;
    private int startSelectionX = -1;
    private int startSelectionY = -1;
    private int currentSelectionX = -1;
    private int currentSelectionY = -1;

    private void onMousePressed(MouseEvent me) {
        if (montage.getNbSignals() == 0) {
            return;
        }

        chosenGraph = null;

        repaintSelectionRectangle();

        startSelectionX = me.getX();
        startSelectionY = me.getY();
        chosenGraph = getGraphGivenItsOrdinate(me.getY());
    }

    private void onMouseReleased(MouseEvent me) {
        if (montage.getNbSignals() == 0) {
            return;
        }

        setCursor(new Cursor(Cursor.DEFAULT_CURSOR));

        if (me.getX() <= startSelectionX) {
            return;
        }
        if (me.getY() == startSelectionY) {
            return;
        }
        if (startSelectionX < 0) {
            return;
        }
        if (startSelectionY < 0) {
            return;
        }
        if (chosenGraph == null) {
            return;
        }

        double width = SwingUtilities.calculateInnerArea(this, new Rectangle()).
                       getWidth();
        double height = SwingUtilities.calculateInnerArea(this, new Rectangle()).
                        getHeight();

        int endSelectionX = me.getX();
        int endSelectionY = me.getY();

        if (me.getX() >= width) {
            endSelectionX = (int) width;
        }
        if (me.getY() >= height) {
            endSelectionY = (int) height;
        }

        if (chosenGraph == getGraphGivenItsOrdinate(endSelectionY)) {
            fftAnalysisDialog.setDataSource(chosenGraph, getStartSecond(),
                                            getEpochDuration(),
                                            (double) startSelectionX / width,
                                            (double) endSelectionX / width);
            fftAnalysisDialog.setVisible(true);
        }

        //startSelectionX = -1;
        //startSelectionY = -1;
        //currentSelectionX = -1;
        //currentSelectionY = -1;
        //chosenGraph = null;
    }

    private void onMouseDragged(MouseEvent me) {
        if (montage.getNbSignals() == 0) {
            return;
        }

        setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
        currentSelectionX = me.getX();
        currentSelectionY = me.getY();

        repaintSelectionRectangle();
    }

    /**
     *
     */
    public void updateTexts() {
        fftAnalysisDialog.updateTexts();
    }

    private JEDFGraph getGraphGivenItsOrdinate(int ordinate) {
        if (montage.getNbSignals() == 0) {
            return null;
        }

        double height = SwingUtilities.calculateInnerArea(this, new Rectangle()).
                        getHeight();
        height /= montage.getNbSignals();

        ordinate /= height;

        JEDFGraph[] graphs = montage.getAllGraph();

        return graphs[ordinate];
    }

    private void drawSelection(Graphics2D g) {
        /*if(startSelectionX<0)
            return;
                 if(startSelectionY<0)
            return;
                 if(currentSelectionX<0)
            return;
                 if(currentSelectionY<0)
            return;
                 if(currentSelectionX<startSelectionX)
            return;
                 if(currentSelectionY<0)
            return;
                 if(chosenGraph==null)
            return;
                 if(chosenGraph!=getGraphGivenItsOrdinate(currentSelectionY))
            return;*/
        if (chosenGraph == null) {
            return;
        }
        if (chosenGraph != getGraphGivenItsOrdinate(currentSelectionY)) {
            return;
        }

        Rectangle selectionRectangle = getSelectionRectangle();

        if (selectionRectangle == null) {
            return;
        }

        g.setStroke(new BasicStroke(3f));
        Color originalColor = g.getColor();

        g.setColor(selectionColor);

        g.draw(selectionRectangle);

        g.setColor(originalColor);

    }

    private Rectangle getSelectionRectangle() {
        if (startSelectionX < 0) {
            return null;
        }
        if (startSelectionY < 0) {
            return null;
        }
        if (currentSelectionX < 0) {
            return null;
        }
        if (currentSelectionY < 0) {
            return null;
        }
        if (currentSelectionX < startSelectionX) {
            return null;
        }
        if (currentSelectionY < 0) {
            return null;
        }

        double ordinate = startSelectionY;
        double height = SwingUtilities.calculateInnerArea(this, new Rectangle()).
                        getHeight();
        height /= montage.getNbSignals();

        ordinate /= height;

        int numGraph = (int) ordinate;

        return new Rectangle(startSelectionX, (int) (numGraph * getGraphHeight()),
                             currentSelectionX - startSelectionX,
                             (int) getGraphHeight());
    }

    private void repaintSelectionRectangle() {
        Rectangle paintRect = getSelectionRectangle();

        if (paintRect == null) {
            return;
        }

        repaint((int) paintRect.getX(), (int) paintRect.getY(),
                (int) paintRect.getWidth() + 50, (int) paintRect.getHeight());
    }
}
